﻿using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using MugenMvvmToolkit.Annotations;
using MugenMvvmToolkit.Infrastructure;
using MugenMvvmToolkit.Interfaces;
using MugenMvvmToolkit.Interfaces.Models;
using MugenMvvmToolkit.Interfaces.ViewModels;
using MugenMvvmToolkit.Models;
#if TASKSUPPORT
using System.Threading.Tasks;
#endif
using MugenMvvmToolkit.Utils;

namespace MugenMvvmToolkit.ViewModels
{
    /// <summary>
    ///     Represents the view model that can contains a collection of other <see cref="IViewModel" />.
    /// </summary>
    [BaseViewModel(Priority = 4)]
    public class MultiViewModel : ViewModelBase, IMultiViewModel
    {
        #region Fields

        private IList<IViewModel> _itemsSource;
        private INotifyCollectionChanged _notifyCollectionChanged;
        private IViewModel _selectedItem;
        private bool _disposeViewModelOnRemove;
        private bool _disposeViewModelsOnDispose;

        #endregion

        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="MultiViewModel" /> class.
        /// </summary>
        public MultiViewModel()
        {
            _disposeViewModelOnRemove = true;
            _disposeViewModelsOnDispose = true;
            // ReSharper disable once DoNotCallOverridableMethodsInConstructor
            ItemsSource = new SyncronizedNotifiableCollection<IViewModel>();
        }

        #endregion

        #region Implementation of IMultiViewModel

        /// <summary>
        ///     Gets or sets the selected view-model.
        /// </summary>
        public virtual IViewModel SelectedItem
        {
            get { return _selectedItem; }
            set
            {
                if (Equals(value, _selectedItem) && (value != null && !ItemsSource.Contains(value)))
                    return;
                UpdateSelectableViewModels(value);
                IViewModel oldValue = _selectedItem;
                _selectedItem = value;
                OnSelectedItemChangedInternal(oldValue, _selectedItem);
                OnPropertyChanged("SelectedItem");
            }
        }

        /// <summary>
        ///     Gets the collection of <see cref="IViewModel" />s.
        /// </summary>
        public virtual IList<IViewModel> ItemsSource
        {
            get { return _itemsSource; }
            set
            {
                Should.PropertyBeNotNull(value, "ItemsSource");
                Should.BeOfType<INotifyCollectionChanged>(value, "ItemsSource");
                if (Equals(_itemsSource, value)) return;
                if (_notifyCollectionChanged != null)
                    _notifyCollectionChanged.CollectionChanged -= OnViewModelsChanged;

                _notifyCollectionChanged = (INotifyCollectionChanged)value;
                _itemsSource = value;
                _notifyCollectionChanged.CollectionChanged += OnViewModelsChanged;
                OnPropertyChanged("ItemsSource");
            }
        }

        /// <summary>
        ///     Gets or sets the value. If <c>true</c> the view-model will disposed child view models when it closed.
        /// </summary>
        public bool DisposeViewModelOnRemove
        {
            get { return _disposeViewModelOnRemove; }
            set
            {
                _disposeViewModelOnRemove = value;
                OnPropertyChanged("DisposeViewModelOnRemove");
            }
        }

        /// <summary>
        ///     Gets or sets the value. If <c>true</c> the view-model will disposed child view models when it will disposed.
        /// </summary>
        public bool DisposeViewModelsOnDispose
        {
            get { return _disposeViewModelsOnDispose; }
            set
            {
                _disposeViewModelsOnDispose = value;
                OnPropertyChanged("DisposeViewModelsOnDispose");
            }
        }

        /// <summary>
        ///     Adds the specified <see cref="IViewModel" /> to <see cref="IMultiViewModel.ItemsSource" />.
        /// </summary>
        /// <param name="viewModel">
        ///     The specified <see cref="IViewModel" />.
        /// </param>
        public virtual void AddViewModel(IViewModel viewModel)
        {
            EnsureIsNotDisposed();
            Should.NotBeNull(viewModel, "viewModel");
            ItemsSource.Add(viewModel);
            SelectedItem = viewModel;
        }

#if TASKSUPPORT
        /// <summary>
        ///     Removes the specified <see cref="IViewModel" /> from <see cref="IMultiViewModel.ItemsSource" />.
        /// </summary>
        /// <param name="viewModel">
        ///     The specified <see cref="IViewModel" />.
        /// </param>
        /// <param name="parameter">The specified parameter, if any.</param>
        public virtual Task<bool> RemoveViewModel(IViewModel viewModel, object parameter = null)
        {
            EnsureIsNotDisposed();
            Should.NotBeNull(viewModel, "viewModel");
            if (!ItemsSource.Contains(viewModel))
                return MvvmUtils.FalseTaskResult;
            var closeableViewModel = viewModel as ICloseableViewModel;
            if (closeableViewModel != null)
                return closeableViewModel.Close(parameter);
            return ItemsSource.Remove(viewModel) ? MvvmUtils.TrueTaskResult : MvvmUtils.FalseTaskResult;
        }
#else
        /// <summary>
        ///     Removes the specified <see cref="IViewModel" /> from <see cref="IMultiViewModel.ItemsSource" />.
        /// </summary>
        /// <param name="viewModel">
        ///     The specified <see cref="IViewModel" />.
        /// </param>
        /// <param name="parameter">The specified parameter, if any.</param>
        public virtual IOperationResult<bool> RemoveViewModel(IViewModel viewModel, object parameter = null)
        {
            EnsureIsNotDisposed();
            Should.NotBeNull(viewModel, "viewModel");
            if (!ItemsSource.Contains(viewModel))
                return MvvmUtils.FalseOperationResult;
            var closeableViewModel = viewModel as ICloseableViewModel;
            if (closeableViewModel != null)
                return closeableViewModel.Close(parameter);
            return ItemsSource.Remove(viewModel) ? MvvmUtils.TrueOperationResult : MvvmUtils.FalseOperationResult;
        }
#endif

        /// <summary>
        ///     Clears all view models from <see cref="IMultiViewModel.ItemsSource" />.
        /// </summary>
        /// <param name="isDisposeViewModel">Indicating that need dispose view models.</param>
        public virtual void Clear(bool isDisposeViewModel)
        {
            EnsureIsNotDisposed();
            foreach (var viewModel in ItemsSource.ToArray().OfType<ICloseableViewModel>())
            {
                viewModel.Closed -= OnViewModelClosedInternal;
                if (!isDisposeViewModel) continue;
                viewModel.Dispose();
            }
            ItemsSource.Clear();
            SelectedItem = null;
        }

        /// <summary>
        ///     Occurs when the <c>SelectedItem</c> property changed.
        /// </summary>
        public virtual event SelectedItemChangedHandler<IViewModel> SelectedItemChanged;

        /// <summary>
        ///     Occurs when the collection changes.
        /// </summary>
        public event NotifyCollectionChangedEventHandler CollectionChanged
        {
            add
            {
                Should.PropertyBeNotNull(ItemsSource, "ItemsSource");
                _notifyCollectionChanged.CollectionChanged += value;
            }
            remove { _notifyCollectionChanged.CollectionChanged -= value; }
        }

        #endregion

        #region Methods

        /// <summary>
        ///     Occurs when the <c>SelectedItem</c> property changed.
        /// </summary>
        /// <param name="oldValue">The old value.</param>
        /// <param name="newValue">The new value.</param>
        protected virtual void OnSelectedItemChanged(IViewModel oldValue, IViewModel newValue)
        {
        }

        /// <summary>
        ///     Occurs when any closeable view model closed.
        /// </summary>
        protected virtual void OnViewModelClosed(IViewModel viewModel, object parameter)
        {
        }

        /// <summary>
        ///     Occurs when any workspace view model changed.
        /// </summary>
        private void OnViewModelsChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            IViewModel newItem = null;
            if (e.NewItems != null && e.NewItems.Count != 0)
            {
                EnsureIsNotDisposed();
                foreach (IViewModel viewModel in e.NewItems)
                {
// ReSharper disable once NotResolvedInText
                    Should.NotBeNull(viewModel, "newItem");
                    var closeableViewModel = viewModel as ICloseableViewModel;
                    if (closeableViewModel != null)
                        closeableViewModel.Closed += OnViewModelClosedInternal;
                    newItem = viewModel;
                }
            }

            if (e.OldItems != null && e.OldItems.Count != 0)
            {
                foreach (IViewModel viewModel in e.OldItems)
                {
// ReSharper disable once NotResolvedInText
                    Should.NotBeNull(viewModel, "oldItem");
                    var closeableViewModel = viewModel as ICloseableViewModel;
                    if (closeableViewModel != null)
                        closeableViewModel.Closed -= OnViewModelClosedInternal;

                    var selectable = viewModel as ISelectable;
                    if (selectable == null || !selectable.IsSelected) continue;
                    selectable.IsSelected = false;
                }
            }
            if (newItem != null)
                SelectedItem = newItem;
        }

        /// <summary>
        ///     Occurs when any closeable view model closed.
        /// </summary>
        private void OnViewModelClosedInternal(IViewModel viewModel, object parameter)
        {
            if (viewModel == null) return;
            if (DisposeViewModelOnRemove)
                viewModel.Dispose();
            var oldIndex = ItemsSource.IndexOf(viewModel);
            ItemsSource.Remove(viewModel);
            if (SelectedItem != viewModel || oldIndex == -1 || ItemsSource.Count == 0) return;
            if (oldIndex >= ItemsSource.Count)
                oldIndex = ItemsSource.Count - 1;
            SelectedItem = ItemsSource[oldIndex];
            OnViewModelClosed(viewModel, parameter);
        }

        private void OnSelectedItemChangedInternal(IViewModel oldvalue, IViewModel newvalue)
        {
            OnSelectedItemChanged(oldvalue, newvalue);
            SelectedItemChangedHandler<IViewModel> handler = SelectedItemChanged;
            if (handler != null) handler(oldvalue, newvalue);
        }

        private void UpdateSelectableViewModels(IViewModel newItem)
        {
// ReSharper disable once ForCanBeConvertedToForeach
            for (int i = 0; i < ItemsSource.Count; i++)
            {
                var viewModel = ItemsSource[i] as ISelectable;
                if (viewModel != null && viewModel.IsSelected && viewModel != newItem)
                    viewModel.IsSelected = false;
            }

            var selectableViewModel = newItem as ISelectable;
            if (selectableViewModel != null && !selectableViewModel.IsSelected)
                selectableViewModel.IsSelected = true;
#if WPF
            if (newItem == null || ThreadManager == null) return;
            ThreadManager.InvokeInUi(() =>
            {
                if (ItemsSource == null) return;
                var collectionView = System.Windows.Data.CollectionViewSource.GetDefaultView(ItemsSource);
                if (collectionView == null) return;
                collectionView.MoveCurrentTo(newItem);
            });
#endif
        }

        #endregion

        #region Overrides of WorkspaceViewModel

        /// <summary>
        ///     Occurs after the initialization of the current <see cref="ViewModelBase" />.
        /// </summary>
        internal override void OnInitializedInternal()
        {
            var notifiableCollection = ItemsSource as SyncronizedNotifiableCollection<IViewModel>;
            if (notifiableCollection != null)
                notifiableCollection.ThreadManager = ThreadManager;
            base.OnInitializedInternal();
        }

        /// <summary>
        ///     Occurs after current view model disposed, use for clear resource and event listeners(Internal only).
        /// </summary>
        internal override void OnDisposeInternal(bool disposing)
        {
            if (disposing)
            {
                if (_notifyCollectionChanged != null)
                    _notifyCollectionChanged.CollectionChanged -= OnViewModelsChanged;
                Clear(DisposeViewModelsOnDispose);
                SelectedItemChangedHandler<IViewModel> onSelectedItemChanged = SelectedItemChanged;
                if (onSelectedItemChanged == null) return;
                foreach (SelectedItemChangedHandler<IViewModel> @delegate in onSelectedItemChanged.GetInvocationList())
                    SelectedItemChanged -= @delegate;
            }
            base.OnDisposeInternal(disposing);
        }

        #endregion
    }
}